Namespace.Use("Application.Net.Core", function(ns) {
    //
    //  Imports.
    //
    var IsAvailable = Namespace.Import("Browser.WebSocket.IsAvailable");
    var Configuration = Namespace.Import("Application.Net.Configuration");

    //
    //  Global.
    //
    var g_Callbacks = [];

    //
    //  Module codes.
    //
    if (IsAvailable()) {
        console.log("[Net/Core] WebSocket is available.");

        //
        //  Global.
        //
        var g_ConnectionInstance = null;

        //
        //  Private APIs.
        //
        function WSOnOpen() {
            console.log("[Net/Core] WebSocket is opened.");
        }

        function WSOnMessage(msg) {
            var parsed;
            try {
                parsed = JSON.parse(msg.data);
            } catch(error) {
                console.error("[Net/Core] Received a corrupted message.");
                return;
            }
            g_Callbacks.forEach(function(callback) {
                callback.call(this, parsed);
            });
        }

        function WSOnError() {
            console.error("[Net/Core] Received an error.");

            //  Close the connection.
            g_ConnectionInstance.close();
        }

        function WSOnClose() {
            console.warn("[Net/Core] WebSocket is closed.");

            //  Reconnect.
            setTimeout(function() {
                console.log("[Net/Core] Reconnecting...");
                WSConnect();
            }, Configuration.GetReconnectDelay());
        }

        function WSFetchData() {
            if (g_ConnectionInstance !== null && g_ConnectionInstance.readyState == WebSocket.OPEN) {
                g_ConnectionInstance.send("fetch");
            }
        }

        function WSConnect() {
            //  Create the connection.
            g_ConnectionInstance = new WebSocket(Configuration.GetConnectionURL());

            //  Bind events.
            g_ConnectionInstance.onopen = WSOnOpen;
            g_ConnectionInstance.onmessage = WSOnMessage;
            g_ConnectionInstance.onerror = WSOnError;
            g_ConnectionInstance.onclose = WSOnClose;
        }

        //  Connect.
        setTimeout(WSConnect, 0);

        //  Create the fetching timer.
        setInterval(WSFetchData, Configuration.GetFetchInterval());
    } else {
        console.error("[Net/Core] WebSocket is unavailable.");
    }

    //
    //  Public APIs.
    //
    var HasCallback = Polymorphic.Create(
        [Function],
        function(callback) {
            for (var idx = 0; idx < g_Callbacks.length; ++idx) {
                if (g_Callbacks[idx] == callback) {
                    return true;
                }
            }
            return false;
        }
    );
    var AddCallback = Polymorphic.Create(
        [Function],
        function(callback) {
            if (HasCallback(callback)) {
                throw "Duplicated callback.";
            }
            g_Callbacks.push(callback);
        }
    );
    var RemoveCallback = Polymorphic.Create(
        [Function],
        function(callback) {
            for (var idx = 0; idx < g_Callbacks.length; ++idx) {
                if (g_Callbacks[idx] == callback) {
                    g_Callbacks.splice(idx, 1);
                    return;
                }
            }
            throw "No such callback.";
        }
    );

    //  Export public APIs.
    Namespace.Export(ns, "HasCallback", HasCallback);
    Namespace.Export(ns, "AddCallback", AddCallback);
    Namespace.Export(ns, "RemoveCallback", RemoveCallback);
});
